package money

import (
	"base"
	"config/table"
	"emma/errors"
	"game_server/logic/asset/api"
	"game_server/logic/asset/handler"
	"game_server/logic/db/event"
	"game_server/logic/db/mark"
	. "game_server/logic/internal"
	"game_server/model"
	"proto/base"
	"proto/money"
)

type moneyChange map[base.AssetItemId]int64
type moneySystem struct {
}

var system = new(moneySystem)

func init() {
	Skeleton.AfterInit(func() {
		asset_api.Register(system)
	})
}

func (*moneySystem) AssetType() base.AssetType {
	return base.AssetType_Money
}

func (*moneySystem) GetAsset(player *model.Player, args *asset_handler.GetArgs) (result base.AssetItemCount, err error) {
	itemId := args.ItemId
	var curCount *base.AssetItemCount
	if curCount, err = system.getCount(player, itemId); err == nil {
		result = *curCount
	}

	return
}

func (*moneySystem) HaveAsset(player *model.Player, args *asset_handler.HaveArgs) (err error) {
	itemId := args.ItemId
	itemCount := args.ItemCount
	if _, err = system.haveAsset(player, itemId, itemCount); err != nil {
		return
	}

	return
}

func (*moneySystem) AddAsset(player *model.Player, args *asset_handler.AddArgs) (err error) {
	itemId := args.ItemId
	itemCount := args.ItemCount
	if err = system.addAsset(player, itemId, itemCount); err != nil {
		return
	}

	change := system.getChange(args.Changes)
	change[itemId] += int64(itemCount)

	return
}

func (*moneySystem) addAsset(player *model.Player, itemId base.AssetItemId, itemCount base.AssetItemCount) (err error) {
	var curCount *base.AssetItemCount
	if curCount, err = system.getCount(player, itemId); err != nil {
		return
	}

	if base.AssetItemMaxCount-itemCount < *curCount {
		code := int(proto_base.ErrorCode_MoneyOverflow)
		err = errors.New(code, "money %v overflow, have %v, new addItem %v", itemId, *curCount, itemCount)
		return
	}

	*curCount += itemCount

	return
}

func (*moneySystem) RemoveAsset(player *model.Player, args *asset_handler.RemoveArgs) (err error) {
	itemId := args.ItemId
	itemCount := args.ItemCount

	if err = system.removeAsset(player, itemId, itemCount); err != nil {
		return
	}
	change := system.getChange(args.Changes)
	change[itemId] -= int64(itemCount)

	return
}

func (*moneySystem) removeAsset(player *model.Player, itemId base.AssetItemId, itemCount base.AssetItemCount) (err error) {
	var curCount *base.AssetItemCount
	if curCount, err = system.haveAsset(player, itemId, itemCount); err != nil {
		return
	}

	*curCount -= itemCount
	return
}

func (*moneySystem) haveAsset(player *model.Player, itemId base.AssetItemId, itemCount base.AssetItemCount) (curCount *base.AssetItemCount, err error) {
	if curCount, err = system.getCount(player, itemId); err != nil {
		return
	}

	if *curCount < itemCount {
		code := int(proto_base.ErrorCode_MoneyNotEnough)
		err = errors.New(code, "money %v not enough, have %v, need %v", itemId, *curCount, itemCount)
		return
	}

	return
}

func (*moneySystem) getChange(changes asset_handler.Changes) (result moneyChange) {
	if value, exists := changes[system]; exists {
		result = value.(moneyChange)
	} else {
		result = make(moneyChange)
		changes[system] = result
	}

	return
}

func (*moneySystem) RollbackAsset(player *model.Player, args *asset_handler.RollbackArgs) {
	var change moneyChange
	if value, exists := args.Changes[system]; exists {
		change = value.(moneyChange)
	} else {
		return
	}

	for itemId, itemCount := range change {
		if itemCount == 0 {
			continue
		} else if itemCount > 0 {
			system.removeAsset(player, itemId, base.AssetItemCount(itemCount))
		} else {
			system.addAsset(player, itemId, base.AssetItemCount(-itemCount))
		}
	}
}

func (*moneySystem) OnAssetChanged(player *model.Player, args *asset_handler.ChangeArgs) {
	Event.Notify(db_event.Collect, player, db_mark.Money)

	player.SetSyncMark(system, true)
	player.DelayCall(func() {
		if !player.HasSyncMark(system) {
			return
		}
		player.SetSyncMark(system, false)
		system.syncChange(player)
	})
}

func (*moneySystem) syncChange(player *model.Player) {
	if !player.Online() {
		return
	}

	response := new(proto_money.SyncChange)
	response.Item = player.Money.ToProto()
	rpc.SyncChange(player, response)
}

func (*moneySystem) getCount(player *model.Player, itemId base.AssetItemId) (result *base.AssetItemCount, err error) {
	switch itemId {
	case config_table.Global.GoldCoinItemId:
		result = &player.Money.GoldCoin
	case config_table.Global.DiamondItemId:
		result = &player.Money.Diamond
	default:
		code := int(proto_base.ErrorCode_UnrealizedMoneyId)
		err = errors.New(code, "unrealized money id: %v", itemId)
		return
	}

	return
}
